let eventCounter = 1;

/**
 * 基础组件. 建议最上层组件这样写:
 *
 * constructor(args) { ... this.context = args.context;  this._tick = this._tick.bind(this); }
 *
 * init() { this.lastHandler = requestAnimationFrame(() => this._tick()); }
 *
 * destroy() { super.destroy(); cancelAnimationFrame(this.lastHandler); }
 *
 * tick() { this.lastHandler = requestAnimationFrame(() => this._tick()); }
 */
class BaseComp {
	/** @type {BaseComp} 如果是 null 或是它自己, 那它就是最上级组件 */
	parent = null;
	/** @type {BaseComp} 最上级组件 */
	root = null;

	/** @type {BaseComp[]} */
	children = [];
	dirty = true;
	/**
	 * @type {number[]} 如果调用 absolutePos() 想将位置重置,
	 * 不是从父组件的位置推自己的位置, 那就把它设置了
	 */
	basePosition = null;

	// 相对于父组件的位置, 相对位置
	x = 0; _x = 0;
	y = 0; _y = 0;

	/** @param {BaseComp} parent */
	constructor(parent) {
		this.parent = parent;
		if (parent != null) {
			parent.children.push(this);
			this.root = parent.root;
		} else {
			this.root = this;
		}

		const thiz = this;
		Object.defineProperty(this, 'x', {
			set(value) {
				if (typeof value != 'number' || isNaN(value) || !isFinite(value)) {
					throw 'BaseComp.x can not be ' + value;
				}
				if (thiz._x == value) return;
				thiz.makeDirty();
				thiz._x = value;
			},
			get() { return thiz._x }
		});
		Object.defineProperty(this, 'y', {
			set(value) {
				if (typeof value != 'number' || isNaN(value) || !isFinite(value)) {
					throw 'BaseComp.y can not be ' + value;
				}
				if (thiz._y == value) return;
				thiz.makeDirty();
				thiz._y = value;
			},
			get() { return thiz._y }
		});
	}

	makeDirty(childDirty = true) {
		this.dirty = true;
		if (childDirty) {
			this.children.forEach(child => child.makeDirty());
		}
	}

	hasInited = false;
	destoryed = false;
	/** @type {function[]} */ defers = null;
	/** @type {((now:number)=>void)[]} */ tickers = [ (now) => this.tick(now) ];

	/**
	 * 添加在 destroy() 执行时顺带需要完成的工作
	 * @param {function} func
	 * @returns {function} 删除这个 defer 的方法
	 */
	defer(func) {
		if (this.defers == null) this.defers = [];
		this.defers.push(func);
		return () => {
			let index = this.defers.findIndex(f => f === func);
			this.defers.splice(index, 1);
		}
	}

	/**
	 * 添加在 tick() 执行时顺带需要完成的工作
	 * @param {(now:number)=>void} func
	 * @returns {function} 删除这个 ticker 的方法
	 */
	ticker(func) {
		this.tickers.push(func);
		return () => {
			let index = this.tickers.findIndex(f => f === func);
			this.tickers.splice(index, 1);
		}
	}

	_init() {
		this.hasInited = true;
		this.init();
		this.children.forEach(child => child._init());
	}

	init() {}

	/**
	 * 只有最上层的 component 要调这个
	 */
	startTick() {
		if (this.parent) {
			throw '只有最上层的 component 能调用 startTick()';
		}

		this._nextTick = this._nextTick.bind(this);
		this.tickHandler = requestAnimationFrame(this._nextTick);
		this.defer(() => cancelAnimationFrame(this.tickHandler));
	}

	_nextTick() {
		this.tickHandler = requestAnimationFrame(this._nextTick);
		this._tick();
	}

	/**
	 * 每次刷新屏幕调用
	 * @param {number} now
	 *   就是当前的时间戳
	 */
	_tick(now) {
		if (!this.hasInited) {
			this._init();
		}

		if (now == null) {
			now = Date.now();
		}

		const tickers = this.tickers.slice();
		for (let i = 0; i < tickers.length; i++) {
			tickers[i].call(this, now);
			if (this.destoryed) return;
		}

		if (this.dirty) {
			this.paint();
			this.dirty = false;
		}
		this.children.forEach(child => child._tick(now));
	}

	/**
	 * @param {number} now 
	 */
	tick(now) {}

	/**
	 * 重新将组件摆放位置
	 */
	paint() {}

	/** @returns {number[]} */
	relativePos() {
		if (this.basePosition) {
			return this.basePosition.concat();
		}
		if (this.parent == null) {
			return [this._x, this._y];
		}
		let parentPos = this.parent.relativePos();
		parentPos[0] += this._x;
		parentPos[1] += this._y;
		return parentPos;
	}

	/** @returns {number[]} */
	absolutePos() {
		if (this.parent == null) {
			return [this._x, this._y];
		}
		let parentPos = this.parent.absolutePos();
		parentPos[0] += this._x;
		parentPos[1] += this._y;
		return parentPos;
	}

	getContext() {
		return this.root.context;
	}

	/** @type {string[]} */
	ons = [];

	/**
	 * 向 root 注册事件
	 * @param {string} key event 名
	 * @param {function} func
	 * @returns {function} 用于解绑该注册事件
	 */
	on(key, func) {
		let id = `compEvt:${eventCounter++}`;
		this.getContext().subscribeOne(key, func, id);
		this.ons.push(id);
		return () => this._off(id);
	}

	_off(id) {
		this.ons.splice(this.ons.findIndex(item => item === id), 1);
		this.getContext().removeSubscriber(id);
	}

	/**
	 * 发布事件
	 * @param {string} key
	 * @param {any} event
	 */
	emit(key, event) {
		this.getContext().releaseEvent(key, event);
	}

	destroy() {
		this.children.slice().forEach(child => child.destroy());
		while (this.ons.length > 0) {
			this._off(this.ons[0]);
		}
		if (this.parent) {
			let index = this.parent.children.findIndex((item) => this === item);
			if (index != -1) {
				this.parent.children.splice(index, 1);
			}
		}
		if (this.defers) {
			this.defers.forEach(func => func.call(this));
		}
		this.destoryed = true;
	}
}

class SingleAnimationComp extends BaseComp {
	animation = 0; _animation = 0;
	/** @type {function} */
	animationFunc = null;
	animationDuration = 500;

	constructor(parent) {
		super(parent);

		const thiz = this;
		Object.defineProperty(this, 'animation', {
			set(value) {
				if (typeof value != 'number' || isNaN(value) || !isFinite(value)) {
					throw 'animation can not be ' + value;
				}
				let v = value > 1 ? 1 : value < 0 ? 0 : value;
				if (thiz._animation == v) return;
				thiz.makeDirty();
				thiz._animation = v;
			},
			get() { return thiz._animation }
		});
	}

	onAnimate(raise = true) {
		// animation 0 ~ 1 的时间是 800ms
		const maxDuration = this.animationDuration;
		const baseTime = Date.now() - (raise ? this.animation : 1 - this.animation) * maxDuration;
		this.animationFunc = () => {
			const now = Date.now();
			const duration = now - baseTime;

			if (duration < maxDuration) {
				this.animation = raise ? duration / maxDuration : 1 - duration / maxDuration;
			} else {
				this.animation = raise ? 1 : 0;
				this.animationFunc = null;
			}
			this.onAnimationUpdate();
		}
	}

	// override
	onAnimationUpdate() {}

	_tick() {
		if (this.animationFunc != null) {
			this.animationFunc.call(this);
		}
		super._tick();
	}
}

export {
	BaseComp, SingleAnimationComp,
};
